home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 1 / Cream of the Crop 1.iso / PROGRAM / TPFORT18.ARJ / TPFORT.DOC < prev   
Text File  |  1992-03-08  |  23KB  |  508 lines

  1. TPFort v 1.82 - A link from Turbo Pascal 5.0-6.0 to MS Fortran 5.0 or 5.1.
  2.  
  3. Copyright (c) 1989,1990,1991 D.J. Murdoch.  All rights reserved.
  4. Portions copyright (c) TurboPower Software.  Used with permission.
  5.  
  6.  
  7. PURPOSE
  8.  
  9. TPFort is a collection of several procedures that allow Microsoft Fortran
  10. routines to be called from Turbo Pascal.  I wrote it so that I could use the
  11. binary-only NAG Fortran library for numerical routines in Turbo Pascal
  12. programs, but it ended up being a general purpose linker.
  13.  
  14.  
  15. PRICE
  16.  
  17. TPFort is free to use and incorporate into your own programs for any purpose.
  18. Distribute it to anyone you like, but please don't remove my copyright notice.
  19. If you modify it in any way, please do not distribute the modified version.
  20. Full source code is included.
  21.  
  22.  
  23. METHOD
  24.  
  25. The Fortran routines are compiled into their own loader file which is loaded at
  26. run time by a Turbo Pascal program, making most of the Fortran subroutines and
  27. functions available to the Pascal program.  The molasses-slow Fortran compiler
  28. and linker need only be run once to create the loader; changes to the Pascal
  29. part of the program don't force recompiling or re-linking of the Fortran part.
  30.  
  31. The Fortran loader will be loaded at the top of the TP heap.  The program
  32. NEEDMEM is provided to print out the memory requirements of the loader. If the
  33. total required is greater than TP's MaxAvail, TPFORT won't run.
  34.  
  35.  
  36. INSTRUCTIONS
  37.  
  38. There are several steps involved in preparing Fortran routines to be called
  39. from Turbo Pascal.
  40.  
  41. 1.   Preparing the Fortran Program
  42.  
  43. Write a Fortran program which includes the following declarations and a call
  44. to CALLTP, in the following format:
  45.  
  46.         EXTERNAL routine1, routine2, ..., routineX
  47.  
  48.         CALL CALLTP(routine1, routine2, ..., routineX, X)
  49.  
  50. where routine1 through to routineX are the names of the Fortran routines you
  51. wish to make available to your Turbo Pascal program, and X is an integer value
  52. giving the number of routines being passed.  The external declaration is
  53. extremely important; if not given, Fortran will assume the routine names are
  54. local integer or real variables, and things will get really messed up.
  55.  
  56. This loader may do anything else, such as reading data from files, allocating
  57. space, etc.  It's not all that important where the call to CALLTP takes place,
  58. but more efficient use will be made of the program stack if the call comes
  59. somewhere in the main program, rather than in a function or subroutine.
  60.  
  61. After this call and any other initialization necessary, the program should
  62. exit.  As this will close any open files, and I/O done while TP is active
  63. is probably unreliable, it should complete any I/O operations before quitting,
  64. and the routines being passed should avoid doing I/O.
  65.  
  66. Compile this routine and link it to the object file CALLTP.OBJ.  I've only
  67. tested TPFORT with the default large memory model, but it should work in the
  68. huge model as well.  It won't work in the medium model, because TPFORT expects
  69. full 4 byte addresses.
  70.  
  71. NB:  With version 5.1 of the Fortran compiler, the loader routine (and possibly
  72.      others) MUST be compiled with the /Gb option for backward compatibility.
  73.      If not, programs will crash on the first call.
  74.  
  75. I recommend using the /FPi87 floating point option, to get the smallest code,
  76. but if you don't have a coprocessor you'll need /FPi or one of the other /FP
  77. options.  (It would be safe to use /FPi87 even if you don't have a
  78. coprocessor, if you link the TP emulator in, but I don't think the Fortran
  79. startup code will run if you don't.)
  80.  
  81. Be sure to specify to the linker that a larger than normal stack will be
  82. needed - I'd suggest a minimum of 16K. The Turbo Pascal program will be using
  83. this stack instead of its own much of the time, and TP makes much heavier use
  84. of the stack than does Fortran.
  85.  
  86. Warning:  Don't try running the loader program on its own, unless you avoid
  87. executing the call to CALLTP.  If TP isn't there to catch that call, you're
  88. very likely to crash.  It might be a good idea to rename the .EXE with a non-
  89. executable extension such as .LDR just to be sure.
  90.  
  91. 2.   Preparing the TP dummy procedures
  92.  
  93. You need to create dummy versions of all the Fortran routines that you want to
  94. call.  They _must_ be declared as "far" routines, either through the use of
  95. the $F+ compiler directive, or by putting them in the interface section of a
  96. unit.  I'd suggest isolating all of them into their own unit and interfacing
  97. them.
  98.  
  99. Each of the dummy routines takes an argument list that corresponds exactly to
  100. the argument list of the Fortran routine.  By default, all Fortran arguments
  101. are passed by reference, so these should be too, by declaring them as "var"
  102. parameters.  The following list gives corresponding types between the two
  103. languages:
  104.  
  105.   Fortran               TP
  106.  
  107.   INTEGER*2             integer
  108.   INTEGER*4             longint
  109.   INTEGER               longint
  110.   REAL                  single
  111.   REAL*4                single
  112.   REAL*8                double
  113.   DOUBLE PRECISION      double
  114.   CHARACTER*n           (special - see note below)
  115.   CHARACTER*(*)         (special - see note below)
  116.   COMPLEX               fort_complex8
  117.   COMPLEX*8             fort_complex8        These types will be declared in
  118.   COMPLEX*16            fort_complex16       the FortLink unit someday
  119.   LOGICAL               fort_logical
  120.   EXTERNAL              (special - see note below)
  121.  
  122. Note also that Fortran and TP use different conventions for the order of
  123. indices in multi-dimensional arrays.  For example, the Fortran array
  124.  
  125.   REAL X(10,20,30)
  126.  
  127. would be declared as
  128.  
  129.   x : array[1..30,1..20,1..10] of single;
  130.  
  131. in TP. Note also that TP (up to version 6.0, at least) has no facility for
  132. variable dimensions on arrays:  to handle an array which is declared as X(N,M)
  133. you have to declare X as a one-dimensional array and handle the indexing
  134. yourself.
  135.  
  136. Thus a call to the NAG matrix inversion routine F01AAF with Fortran
  137. declaration
  138.  
  139.  SUBROUTINE F01AAF(A, IA, N, UNIT, IUNIT, WKSPCE, IFAIL)
  140.  INTEGER IA, N, IUNIT, IFAIL
  141.  REAL*8 A(IA,N), UNIT(IUNIT,N), WKSPCE(N)
  142.  
  143. would be simulated with dummy declarations something like
  144.  
  145.  procedure f01aaf(var a:realarray;       { realarray is declared in the
  146.                                            FortLink unit }
  147.                   var ia, n:longint;
  148.                   var unit:realarray;
  149.                   var iunit:longint;
  150.                   var wkspce:realarray;
  151.                   var ifail:longint);
  152.  
  153. and element A(I,J) would be addressed at a[i+(j-1)*ia].
  154.  
  155. The content of the dummy TP routine is very simple, and should not be varied.
  156. If the Fortran routine is a SUBROUTINE, use a definition like
  157.  
  158.   const
  159.     f01aaf_num = 1;  { this is the position of F01AAF in the call to CALLTP }
  160.  
  161.   procedure f01aaf;
  162.   begin
  163.     callfort(f01aaf_num);
  164.   end;
  165.  
  166. If desired, additional instructions can be put before the call to callfort;
  167. however, no local variables may be declared and no instructions may follow the
  168. call.
  169.  
  170. If the Fortran routine is a FUNCTION, what to do depends on the function's
  171. type.  Fortran and TP agree on the convention for returning values up to 4
  172. bytes (except singles/REAL*4), so callfort can be used for these functions.
  173. The most common would be a Fortran INTEGER function being declared as a TP
  174. longint function and using callfort.
  175.  
  176. However, Fortran and TP use different conventions for other return types, and
  177. you need to use special calls to do the conversion. If the Fortran routine is
  178. a REAL*8-valued FUNCTION, the "fdouble" procedure replaces callfort.  Use
  179. "fsingle" for REAL*4 values.  For example, for the Gaussian random number
  180. generator G05DDF, the Fortran declaration is
  181.  
  182.  REAL*8 FUNCTION G05DDF(A, B)
  183.  REAL*8 A, B
  184.  
  185. and the TP declarations are
  186.  
  187.  function g05ddf(var a,b:double):double;
  188.  
  189. with implementation
  190.  
  191.  const g05ddf_num = 2;
  192.  function g05ddf;
  193.  begin
  194.    fdouble(g05ddf_num);   { Note that this is a procedure! }
  195.  end;
  196.  
  197. Other structured types can also be returned with some care.  You have to
  198. declare the dummy function to be a pointer to the appropriate type, and use
  199. the "fpointer(procnum)" call to the Fortran routine.  TPFORT only reserves
  200. 8 bytes of space for return values, but larger values can be returned with
  201. some trickery as described in FORTLINK.DOC in the header for fpointer.
  202.  
  203.  
  204. 3.  Preparing the TP main program
  205.  
  206. Once you have your dummy procedure unit set up, you have to make some
  207. modifications to the main program to link in the Fortran at run-time.
  208. This is all done in a single call to
  209.  
  210.  function LoadFort(prog:string;TPentry:pointer):boolean;
  211.  
  212. The prog argument should contain a string giving the fully qualified name of
  213. the Fortran program to be loaded; TPentry should give the address of a TP
  214. routine taking no arguments, which is going to do all the calculations with
  215. the Fortran routines.  It's necessary to do things this way because the call
  216. to LoadFort switches over to the Fortran stack; TPentry^ and any routine it
  217. calls must be able to execute there.  If LoadFort is successful, it won't exit
  218. until TPentry^ returns, and it'll give a True return value.  If it fails
  219. for some reason, it'll print a message and return a False value.  In this
  220. case TPEntry^ wasn't executed at all.  It's possible to call LoadFort several
  221. times if you want to switch in and out of "Fortran mode", though I don't know
  222. any reason to do so.  Only the first time will load anything, but they'll all
  223. attempt to switch to Fortran mode.  Be sure never to call a Fortran
  224. routine when you're not in Fortran mode, or you're likely to crash (or at
  225. least get garbage out). To help determine the current status, the FortLink
  226. unit interfaces two variables:
  227.  
  228.   fortloaded     : boolean;    { True indicates Fortran routines are in memory }
  229.   fortsafe       : boolean;    { True indicates you're in Fortran mode }
  230.  
  231. If you like, you can put tests of fortsafe into your dummy routines before
  232. the callfort or fdouble calls, to abort if there's a problem.
  233.  
  234. If you want to reclaim the memory used by your loader, call UnloadFort
  235. when you're *not* in Fortran mode.  If you call it in Fortran mode, the
  236. call will be ignored.  You might want to use this feature to load
  237. different Fortran libraries; only one may be loaded at a time.
  238.  
  239.  
  240. PASSING STRINGS AND CHARACTER VARIABLES
  241.  
  242. In Turbo Pascal, the "string" type stores the current length in the first
  243. byte.  MS Fortran doesn't have a corresponding type; however, it does allow
  244. variable length character variables to be passed to a routine with the
  245. "CHARACTER*(*)" declaration.  The way this is handled internally is
  246. undocumented, as far as I know, but I've tried to make a guess and my tests
  247. seem to work.
  248.  
  249. It appears that any time CHARACTER variables are passed as arguments to a
  250. SUBROUTINE or FUNCTION, their lengths are stored in a table of word-sized
  251. values.  A pointer to this table is stored at a fixed location, in the global
  252. variable __fcclenv.  To give access to this table, FORTLINK sets up a
  253. pointer to __fcclenv in the global Size_Table.
  254.  
  255. Thus, to pass CHARACTER variables to a Fortran routine, two extra steps are
  256. necessary.  First, you have to construct a table of the current lengths of
  257. each CHARACTER argument; second, you have to set __fcclenv to point to it.
  258. Typical code to pass strings s1 and s2 to Fortran procedure UCHAR would be
  259.  
  260.  type
  261.    my_length_rec = record
  262.      dummy,     { I don't know what the first entry in the table is for }
  263.      s1_length,
  264.      s2_length : word;
  265.    end;
  266.  var
  267.    my_lengths : my_length_rec;
  268.    s1,s2 : string;
  269.  
  270.  begin
  271.    with my_lengths do
  272.    begin
  273.      s1_length := length(s1);
  274.      s2_length := length(s2);
  275.    end;
  276.    Size_Table^ := @my_lengths;    { Note the ^ and @ ! Size_Table itself
  277.                                     should never be changed }
  278.  
  279.    Uchar(..{non character args}...,
  280.          s1[1], ....{more non characters} ...,  { Skip the length byte! }
  281.          s2[1], ....{etc.} );
  282.  
  283. In the TP declaration of Uchar, the character arguments could be type char, or
  284. untyped.  The same sort of technique would be used to pass arrays of char to
  285. Fortran.
  286.  
  287. As described below, it's also possible to have Fortran call TP procedures and
  288. functions.  In this case, the TP procedure should save the sizes of any
  289. character arguments just after Enter_Pascal is called; the Size_Table^ pointer
  290. will be changed by any activity in Fortran at all. The length of the first
  291. character argument will be in Size_Table^^[1]; others will be in appropriate
  292. places in the array.
  293.  
  294. Note:  Because all of the above is undocumented by Microsoft, there may be
  295. mistakes.  Test it carefully before you depend on it!
  296.  
  297.  
  298. PASSING FUNCTION REFERENCES
  299.  
  300. It is possible to pass function or procedure references to a Fortran routine,
  301. but it's a little tricky.
  302.  
  303. The Fortran setup is just the same as for any other kind of routine.  Just
  304. pass the procedure name in CALLTP.
  305.  
  306. The dummy definition is much the same.  Declare the parameter which is the
  307. routine being passed as type "extval", passed by value.
  308.  
  309. The main routine then calculates the reference extval using a call to
  310. "fort_external(procnum)", where procnum is the number of a Fortran procedure
  311. being passed, or "pas_external(@proc)", where proc is the TP procedure
  312. being passed, and saves the answer in a temporary variable.  It passes this
  313. variable to the dummy routine.
  314.  
  315. The main bug in this procedure is that fort_external and pas_external allocate
  316. space for a pointer on the stack, and leave it there.  Thus you can't execute
  317. them in the middle of an expression, or it will get messed up. You should call
  318. the routine Clean_External as soon as possible after using the temporary
  319. variable, to restore the stack to normal.  Call it once for each call you made
  320. to fort_external or pas_external.  In a loop it's probably safe to calculate
  321. the temporary once at the beginning and only clean it up once at the end.  You
  322. MUST reassign the temporary variable every time you enter the routine that
  323. uses it, because its value becomes worthless as soon as you call
  324. Clean_external or exit.
  325.  
  326. There's another bug in pas_external - the TP routine will be executed fully in
  327. the Fortran context.  In particular, this means all global references will
  328. reference the wrong data segment, and TP is likely to overwrite registers that
  329. Fortran expects to have preserved.  To fix this up, at the very beginning you
  330. must call Enter_Pascal, and you must call Leave_Pascal just before exiting.
  331. This temporarily restores the TP context, and saves some registers. Note that
  332. stack checking has to be disabled in a routine being passed this way, since
  333. the stack checker makes a reference to the global System.Stacklimit, and gets
  334. executed before Enter_Pascal.
  335.  
  336. Calls back to Fortran routines are allowed.  Note that only dummy procedures
  337. and functions defined with callfort may be recursive; functions using fsingle,
  338. fdouble or fpointer can not be.  For example, if the Fortran REAL*8 FUNCTION
  339. Minimizer gets passed the TP Myfunction to minimize, then Myfunction can't
  340. call Minimizer, but it can call some other Fortran routine.  This isn't such a
  341. large restriction though, because most Fortran routines don't allow recursive
  342. calls anyways.  (Actually there's a way around this:  pass the Fortran
  343. function through CALLTP several times.  If the Fortran routine could handle
  344. recursive calls normally, then the separate dummy functions will be able to
  345. call each other.)
  346.  
  347. It's also possible to call a routine that was passed in as an Extval:  use the
  348. Ext_Pointer function to convert it into a procedure pointer, and call that.
  349. For example, the following TP routine might be called from Fortran:
  350.  
  351.   procedure callit(routine:extval);
  352.   type
  353.     proc_type = procedure(i:integer);   { Declare a procedure type }
  354.   var
  355.     the_routine : ^proc_type;           { and a pointer to it }
  356.   begin
  357.     the_routine := ext_pointer(routine);   { Convert the extval to a pointer }
  358.     the_routine^(5);                       { and call it with argument 5 }
  359.   end;
  360.  
  361. This works well as long as the routine being passed is known to be a TP
  362. routine.  If it's Fortran, you must "Leave_Pascal" before the call and
  363. "Enter_Pascal" after it.  The catch is that once you Leave_Pascal, you won't
  364. have access to the TP data segment.  Only local variables will be accessible.
  365. If the function could be written in either language, it's probably safest to
  366. assume Fortran, and be sure that any TP routine that might be passed uses the
  367. Enter_Pascal and Leave_Pascal routines.  They're safe to use in any TP
  368. routine, whether it's being called from TP or Fortran.
  369.  
  370. Note 1:  Leave_Pascal will only restore the Fortran context if Enter_Pascal
  371. was used to save it.  This means you can't call a Fortran routine using the
  372. Ext_Pointer pointer unless you're in a routine that was called by Fortran.  I
  373. don't think this is a big restriction, however, since you wouldn't normally
  374. need to use an Extval at all, unless you're dealing pretty closely with
  375. Fortran.
  376.  
  377. Note 2: The method of passing routines works for TP functions only if they use
  378. the same function-value passing convention as Fortran.  Effectively this means
  379. only char, integer and longint valued functions may be passed. There's no way
  380. to call most other TP functions, but it's possible to construct TP functions
  381. which simulate any Fortran function.  Fortran expects the caller to allocate
  382. temporary space for larger return values and expects the function to put the
  383. value there.  So, to write a TP routine that looks to Fortran as though it has
  384. the declaration
  385.  
  386.   REAL*8 FUNCTION SUMSQ(N,X)
  387.   INTEGER N
  388.   REAL*8 X(N)
  389.  
  390. write the header as follows:
  391.  
  392.  function sumsq(var n:longint; var x:realarray; { Mimic the Fortran parameters
  393.                                                   first }
  394.          value_ofs:word):double_ptr;     { Always add another word for the
  395.                                            return address, and return a
  396.                                            pointer. "double_ptr" is a
  397.                                            pointer to a double declared in
  398.                                            FortLink }
  399.  
  400. See the sample program for the rest of the details.
  401.  
  402.  
  403. EXAMPLE
  404.  
  405. A sample program is contained in the following files:
  406.  
  407.   PSAMPLE.PAS           The TP source for the main program
  408.   FSAMPLE.FOR           The MS Fortran source for the loader & routines
  409.   FSAMPLE.PAS           The dummy definitions for FSAMPLE
  410.  
  411. Also included is a Borland style MAKEFILE that compiles both parts.
  412.  
  413. Warning:  There's a bug in MS Fortran 5.0 which means the sample program won't
  414. run on some XT machines.  If you crash when you try to run it, read about the
  415. problem and a patch to fix it in FORTRAN.BUG.
  416.  
  417.  
  418. SUPPRESSING FORTRAN ERRORS
  419.  
  420. You can suppress many Fortran intrinsic errors by reprogramming the
  421. coprocessor, but that doesn't always work. (This is a bug in MS Fortran,
  422. not in TPFORT. MS gives no way to suppress some errors.) There's a
  423. variable called _MERRQQ which appears to suppress the abort when set to
  424. a non-zero value; FortErrorFlag is a pointer to it.
  425.  
  426. The MS Fortran 5.1 documentation claims to allow more control over errors than
  427. was allowed in version 5.0.  I don't know if it works, or whether the method
  428. above is still necessary.
  429.  
  430.  
  431. LIMITATIONS
  432.  
  433. I had real doubts that Fortran I/O would work properly when called from TP,
  434. but have successfully used it in several programs.  Still, I mistrust it.
  435.  
  436. Because Fortran keeps so much data in the stack segment, you might not be able
  437. to increase the stack size large enough.
  438.  
  439.  
  440. FILES
  441.  
  442. The following files should be included here:
  443.  
  444. MAKEFILE        A Borland style make file for the demo.  Just run MAKE.
  445. TPFORT   DOC    This file
  446. FSAMPLE  FOR    The demo Fortran source code
  447. CALLTP   ASM    A86 source code for CALLTP
  448. CALLTP   OBJ    The object code to be linked to the Fortran routine
  449. FSAMPLE  PAS    The dummy definitions to access the Fortran code
  450. PSAMPLE  PAS    The demo TP source code
  451. FORTLINK PAS    Source to the main TPFORT unit
  452. CALLFORT ASM    A86 source code for external routines in FORTLINK
  453. CALLFORT OBJ    Object file for linking into FORTLINK.TPU
  454. OPRO     INC    A few routines extracted (with permission) from the
  455.                 Object Professional library
  456. FORTRAN  BUG    Description of a bug and a patch for MS FORTRAN 5.0
  457. NEEDMEM  EXE    Program to determine memory requirements of Fortran loader
  458.  
  459.  
  460. COMMENTS AND QUESTIONS
  461.  
  462. Send any comments and/or bug reports to me at one of the following addresses:
  463.  
  464.   Duncan Murdoch
  465.   79 John St W
  466.   Waterloo, Ontario, Canada
  467.   N2L 1B7
  468.  
  469.   Internet:  dmurdoch@watstat.waterloo.edu
  470.   Fidonet:   dj murdoch at 1:221/177.40
  471.   Compuserve: 71631,122
  472.  
  473.  
  474. SOURCE CODE
  475.  
  476. TPFORT is a mixture of Turbo Pascal and A86 assembler. A few Object
  477. Professional routines have been included in the file OPRO.INC with the
  478. kind permission of Kim Kokkonen, of TurboPower Software.  If you have
  479. Object Professional, you can set the OPRO_VER compiler variable, and the
  480. real OPro routines will be linked instead.
  481.  
  482. If you don't own Object Professional, leave OPRO_VER undefined and
  483. OPRO.INC will be automatically included.  However, if you don't own
  484. Object Professional you're really missing out; I'd suggest buying it.
  485. You can contact TurboPower at 800-333-4160 or 719-260-6641 (voice),
  486. 719-260-7151 (fax), or by email to Compuserve ID 76004,2611 (that's
  487. 76004.2611@compuserve.com on Internet).
  488.  
  489.  
  490. WARRANTY
  491.  
  492. There is no warranty of any kind with this program.  Use it for free; I hope
  493. you get some value out of it.
  494.  
  495. RELEASE HISTORY
  496.  
  497.  1.82 -  Added OPRO.INC to distribution; added UnLoadFort procedure.
  498.  1.81 -  Fixed MAKEFILE errors, added check for valid proc addresses
  499.  1.8  -  Cleaned up memory management, added version tests and Loaderror
  500.          variable and messages
  501.  1.7  -  added FortErrorFlag
  502.  1.6  -  saves coprocessor or emulator state before running Fortran loader
  503.  1.5  -  corrected bugs, and added Ext_Pointer function
  504.  1.4  -  corrected support for CHARACTER types
  505.  1.3  -  fixed bug to allow large Fortran programs, added NEEDMEM program
  506.  1.2  -  added support for floating point emulation.
  507.  1.1  -  added support for limited recursion, and many function return types.
  508.  1.0  -  first release.